package com.lqwu.mallchatserver.chat.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjUtil;
import com.lqwu.mallchatserver.chat.model.dto.*;
import com.lqwu.mallchatserver.chat.model.entry.Contact;
import com.lqwu.mallchatserver.chat.model.entry.Message;
import com.lqwu.mallchatserver.chat.model.enums.GroupRoleAPPEnum;
import com.lqwu.mallchatserver.chat.model.vo.ChatMemberListResp;
import com.lqwu.mallchatserver.chat.model.vo.ChatRoomResp;
import com.lqwu.mallchatserver.chat.model.vo.MemberResp;
import com.lqwu.mallchatserver.chat.model.vo.RoomBaseInfo;
import com.lqwu.mallchatserver.chat.service.ChatService;
import com.lqwu.mallchatserver.chat.service.ContactService;
import com.lqwu.mallchatserver.chat.service.MessageService;
import com.lqwu.mallchatserver.chat.service.RoomAppService;
import com.lqwu.mallchatserver.chat.service.cache.GroupMemberCache;
import com.lqwu.mallchatserver.chat.service.cache.HotRoomCache;
import com.lqwu.mallchatserver.chat.service.cache.RoomCache;
import com.lqwu.mallchatserver.common.domain.vo.request.CursorPageBaseReq;
import com.lqwu.mallchatserver.common.domain.vo.response.CursorPageBaseResp;
import com.lqwu.mallchatserver.common.event.GroupMemberAddEvent;
import com.lqwu.mallchatserver.common.exception.BusinessException;
import com.lqwu.mallchatserver.common.exception.GroupErrorEnum;
import com.lqwu.mallchatserver.common.service.cache.RoomGroupCache;
import com.lqwu.mallchatserver.common.utils.AssertUtil;
import com.lqwu.mallchatserver.user.model.entry.GroupMember;
import com.lqwu.mallchatserver.user.model.entry.Room;
import com.lqwu.mallchatserver.user.model.entry.RoomGroup;
import com.lqwu.mallchatserver.user.model.entry.User;
import com.lqwu.mallchatserver.user.model.enums.HotFlagEnum;
import com.lqwu.mallchatserver.user.service.GroupMemberService;
import com.lqwu.mallchatserver.user.service.RoomGroupService;
import com.lqwu.mallchatserver.user.service.RoomService;
import com.lqwu.mallchatserver.user.service.UserService;
import com.lqwu.mallchatserver.user.service.cache.UserCache;
import com.lqwu.mallchatserver.user.service.cache.UserInfoCache;
import com.lqwu.mallchatserver.websocket.model.vo.response.WSBaseResp;
import com.lqwu.mallchatserver.websocket.model.vo.response.data.ChatMemberResp;
import com.lqwu.mallchatserver.websocket.model.vo.response.data.WSMemberChange;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class RoomAppServiceImpl implements RoomAppService {
    @Resource
    private HotRoomCache hotRoomCache;
    @Resource
    private ContactService contactService;
    @Resource
    private RoomService roomService;
    @Resource
    private MessageService messageService;
    @Resource
    private RoomGroupService roomGroupService;
    @Resource
    private GroupMemberService groupMemberService;
    @Resource
    private RoomGroupCache roomGroupCache;
    @Resource
    private RoomCache roomCache;
    @Resource
    private UserCache userCache;
    @Resource
    private GroupMemberCache groupMemberCache;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ChatService chatService;
    @Resource
    private UserInfoCache userInfoCache;
    @Resource
    private UserService userService;

    /**
     * 获取会话列表--支持未登录态
     * @param request
     * @param uid
     */
    @Override
    public CursorPageBaseResp<ChatRoomResp> getContactPage(CursorPageBaseReq request, Long uid) {
        // 展示用户要展示的会话列表
        CursorPageBaseResp<Long> page;
        // 1.根据用户是否登录获取会话列表
        if (ObjUtil.isNotNull(uid)){
            // 用户登录了
            Double hotEnd = getCursorOrNull(request.getCursor());
            Double hotStart = null;
            // 2.获取用户基础会话
            CursorPageBaseResp<Contact> contactPage = contactService.getContactPage(uid, request);
            // 3.获取回话房间id集合
            List<Long> roomIdList = contactPage.getList().stream().map(Contact::getRoomId).collect(Collectors.toList());
            if (contactPage.getIsLast()){
                hotStart = getCursorOrNull(contactPage.getCursor());
            }
            // 4.获取热门房间回话
            Set<ZSetOperations.TypedTuple<String>> typedTuples = hotRoomCache.getRoomRange(hotStart, hotEnd);
            // 5.获取回话id集合
            List<Long> hotRoomIds = typedTuples.stream()
                    .map(ZSetOperations.TypedTuple::getValue)
                    .filter(Objects::nonNull)
                    .map(Long::parseLong)
                    .collect(Collectors.toList());
            // 6.合并会话id集合
            roomIdList.addAll(hotRoomIds);
            page = CursorPageBaseResp.init(contactPage,roomIdList);
        }else {
            // 用户未登录,只查全员房间
            CursorPageBaseResp<Pair<Long, Double>> roomCursorPage = hotRoomCache.getRoomCursorPage(request);
            List<Long> roomList = roomCursorPage.getList().stream().map(Pair::getKey).collect(Collectors.toList());
            page = CursorPageBaseResp.init(roomCursorPage,roomList);
        }
        // 7.最后组装会话信息（名称，头像，未读数等）
        List<ChatRoomResp> result = buildContactResp(uid, page.getList());
        return CursorPageBaseResp.init(page, result);
    }

    /**
     * 获取用户所在群组详情
     * @param uid
     * @param roomId
     * @return
     */
    @Override
    public MemberResp getGroupDetail(Long uid, Long roomId) {
        // 1.从缓存中获取群组信息
        RoomGroup roomGroup = roomGroupCache.get(roomId);
        Room room = roomCache.get(roomId);
        AssertUtil.isNotEmpty(roomGroup, "roomId有误");
        Long onlineNum;
        // 2.判断群组是否是热点群
        if (isHotGroup(room)) {// 热点群从redis取人数
            onlineNum = userCache.getOnlineNum();
        } else {
            onlineNum = roomGroupService.getOnlineNum(roomId);
        }
        // 3.获取群组成员角色
        GroupMember groupMember = groupMemberService.lambdaQuery().eq(GroupMember::getGroupId, roomId)
                .eq(GroupMember::getUid, uid)
                .one();
        // 4.返回结果
        return MemberResp.builder()
                .avatar(roomGroup.getAvatar())
                .roomId(roomId)
                .groupName(roomGroup.getName())
                .onlineNum(onlineNum)
                .role(groupMember.getRole())
                .build();
    }

    /**
     * 删除群组成员
     * @param uid
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delMember(Long uid, MemberDelReq request) {
        // 1.参数校验
        // 获取房间信息、房间群信息
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "roomId有误");
        RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, "roomId有误");
        // 获取群中uid对应成员
        GroupMember member = groupMemberService.getMember(request.getRoomId(), uid);
        AssertUtil.isNotEmpty(member, GroupErrorEnum.USER_NOT_IN_GROUP);

        // 2.该成员是否有权限
        // 2.1 判断移除目标是否已被移出
        Long requestUid = request.getUid();
        GroupMember requestMember = groupMemberService.getMember(request.getRoomId(), requestUid);
        AssertUtil.isNotEmpty(requestMember, GroupErrorEnum.USER_NOT_IN_GROUP);
        // 2.2 如果移除目标是群主，失败
        AssertUtil.isFalse(requestMember.getRole().equals(GroupRoleAPPEnum.LEADER.getType()),GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        // 2.3 如果移除目标是管理员，用户需为群主
        if (requestMember.getRole().equals(GroupRoleAPPEnum.MANAGER.getType())){
            boolean equals = requestMember.getRole().equals(GroupRoleAPPEnum.LEADER.getType());
            AssertUtil.isTrue(equals,GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        }
        // 2.4 如果移除目标是普通成员，用户需为群主或管理员
        if (requestMember.getRole().equals(GroupRoleAPPEnum.MEMBER.getType())){
            boolean equals = requestMember.getRole().equals(GroupRoleAPPEnum.LEADER.getType())
                    || requestMember.getRole().equals(GroupRoleAPPEnum.MANAGER.getType());
            AssertUtil.isTrue(equals,GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
        }

        // 3.移除该群成员
        boolean removed = groupMemberService.removeById(member.getId());
        AssertUtil.isTrue(removed,"删除群成员时出错");

        // 4.发送移除事件通知群成员
        List<Long> memberUidList = groupMemberCache.getMemberUidList(roomGroup.getRoomId());
        // todo 推送消息,使用MQ
//        WSBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), member.getUid());
//        pushService.sendPushMsg(ws, memberUidList);
        groupMemberCache.evictMemberUidList(room.getId());
    }

    /**
     * 新增群组
     * @param uid 群主id
     * @param request 新增群组参数
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addGroup(Long uid, GroupAddReq request) {
        // 1.参数校验
        if (ObjUtil.isNull(uid) || ObjUtil.isNull(request)) throw new BusinessException("参数错误");
        // 2.创建群组
        RoomGroup groupRoom = roomService.createGroupRoom(uid);
        // 3.批量添加群组成员
        List<Long> uidList = request.getUidList();
        // 收集成groupMemberList
        List<GroupMember> groupMemberList = uidList.stream().map(a -> {
            GroupMember member = new GroupMember();
            member.setGroupId(groupRoom.getId());
            member.setUid(a);
            member.setRole(GroupRoleAPPEnum.MEMBER.getType());
            return member;
        }).collect(Collectors.toList());
        boolean saveBatch = groupMemberService.saveBatch(groupMemberList);
        AssertUtil.isTrue(saveBatch, "添加群成员时时出错");
        // 4.发送邀请加群消息==》触发每个人的会话
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, groupRoom, groupMemberList, uid));
        return groupRoom.getId();
    }

    /**
     * 邀请加入群组
     * @param uid 用户id
     * @param request
     */
    @Override
    public void addMember(Long uid, MemberAddReq request) {
        // 1.参数校验
        if (ObjUtil.isNull(uid) || ObjUtil.isNull(request)) throw new BusinessException("参数错误");
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "roomId有误");
        AssertUtil.isFalse(isHotGroup(room),  "群组为全员群，无需加入");
        RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, "roomId有误");
        GroupMember member = groupMemberService.getMember(request.getRoomId(), uid);
        AssertUtil.isNotEmpty(member, "你不是该群聊成员");
        RLock lock = redissonClient.getLock("addMember:" + uid);
        try {
            // 竞争分布式锁，等待 3 秒，15 秒自动释放
            boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
            if (res){
                // 抢到锁的业务才能执行 AI 调用
                // 2.判断邀请对象是否在群组中
                List<Long> uidList = request.getUidList();
                // 过滤掉已加入的
                List<Long> toAddList = uidList.stream().filter(a -> {
                    GroupMember groupMember = groupMemberService.getMember(roomGroup.getId(), a);
                    return groupMember == null;
                }).collect(Collectors.toList());
                // 3.批量添加群组成员
                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                        // 3.1 要邀请的用户列表为空，结束
                        if (CollectionUtil.isEmpty(toAddList)) return;
                        // 3.2 用户加到群聊
                        List<GroupMember> groupMemberList = toAddList.stream().map(a -> {
                            GroupMember m = new GroupMember();
                            m.setGroupId(roomGroup.getId());
                            m.setUid(a);
                            m.setRole(GroupRoleAPPEnum.MEMBER.getType());
                            return m;
                        }).collect(Collectors.toList());
                        boolean saveBatch = groupMemberService.saveBatch(groupMemberList);
                        AssertUtil.isTrue(saveBatch, "添加群成员时时出错");
                        // 4.发送邀请加群消息==》触发每个人的会话
                        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, roomGroup, groupMemberList, uid));
                    }
                });
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock != null && lock.isLocked()) {
                if(lock.isHeldByCurrentThread()) {
                    // 记得释放锁
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 游标分页查询群组成员列表
     * @param request
     * @return
     */
    @Override
    public CursorPageBaseResp<ChatMemberResp> getMemberPage(MemberReq request) {
        // 1.参数校验
        AssertUtil.isNotEmpty(request, "参数错误");
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "roomId有误");
        // 2.判断是否为全员群
        List<Long> memberUidList;
        if (isHotGroup(room)){// 全员群展示所有用户
            memberUidList = null;
        }else {// 只展示房间内的群成员
            RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
            memberUidList = groupMemberService.getMemberUidList(roomGroup.getId());
        }
        return chatService.getMemberPage(request, memberUidList);
    }

    /**
     * 获取群组成员列表
     * @param request
     * @return
     */
    @Override
    @Cacheable(cacheNames = "member", key = "'memberList.'+#request.roomId")
    public List<ChatMemberListResp> getMemberList(ChatMessageMemberReq request) {
        // 1.参数校验
        Room room = roomCache.get(request.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号有误");
        // 2.判断是否为总群聊
        if (isHotGroup(room)) {// 全员群展示所有用户100名
            // 2.1 获取群成员列表
            List<User> memberList = userService.getMemberList();
            // 2.2 构建返回结果
            return memberList.stream().map(user -> {
                ChatMemberListResp resp = new ChatMemberListResp();
                resp.setUid(user.getId());
                resp.setName(user.getName());
                resp.setAvatar(user.getAvatar());
                return resp;
            }).collect(Collectors.toList());
        } else {
            // 2.1 从缓存中获取群聊
            RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
            // 2.2 获取群聊成员
            List<Long> memberUidList = groupMemberService.getMemberUidList(roomGroup.getId());
            // 2.3 批量获取用户信息
            Map<Long, User> batch = userInfoCache.getBatch(memberUidList);
            ArrayList<User> userList = new ArrayList<>(batch.values());
            return userList.stream().map(user -> {
                ChatMemberListResp resp = new ChatMemberListResp();
                resp.setUid(user.getId());
                resp.setName(user.getName());
                resp.setAvatar(user.getAvatar());
                return resp;
            }).collect(Collectors.toList());
        }
    }

    /**
     * 判断群组是否是热点群
     * @param room
     * @return
     */
    private boolean isHotGroup(Room room) {
        return room.getHotFlag().equals(HotFlagEnum.YES.getType());
    }


    // todo 获取回话列表太复杂了
    /**
     * 根据uid、房间id集合，组装会话信息
     * @param uid 用户id
     * @param list 房间id集合
     * @return
     */
    private List<ChatRoomResp> buildContactResp(Long uid, List<Long> list) {
        return null;
    }

    /**
     * 获取房间基础信息
     * @param roomIds 房间id集合
     * @param uid 用户id
     * @return 房间基础信息(id - 房间基础信息)
     */
    private Map<Long, RoomBaseInfo> getRoomBaseInfoMap(List<Long> roomIds, Long uid) {
        return null;
    }

    /**
     * 获取cursor，为ZSet中的分数，因此为Double类型
     * @param cursor
     * @return
     */
    private Double getCursorOrNull(String cursor) {
        return Optional.ofNullable(cursor).map(Double::parseDouble).orElse(null);
    }
}
